Repository navigation
Close the active segment before compaction removes source segments - #6
Conversation
compact() deletes every source segment, including the one currently open
for appends. LittleFS and FatFS refuse to unlink an open file, so on those
filesystems the active segment survived compaction ("Failed to unlink ...
Has open FD") and on POSIX-like filesystems the handle silently kept
writing into an unlinked inode.
Close the active file before phase 1 and reopen a segment on both exit
paths: seg0 after a successful compaction (the internal callers then move
on to seg1 as before), current_segment after a failed one, so a direct
compact() call never leaves the store without a writable segment.
Adds test_file_store_compact_keeps_store_writable, which fails on master
(the record written after compact() is lost on reload) and passes here.
The upstream fixes are now PRs (attermann/microReticulum#82 and #85, attermann/microStore#6) and live on our forks, so the default build pulls dobrevit/microReticulum#retimesh/combined and dobrevit/microStore#fix/close-active-segment-before-compaction from git and relies on the new APIs unconditionally: - Reticulum::jobs_interval(1.0f) replaces the manual Transport::jobs() call in the RNS task, - the packet-carrying AnnounceHandler callback replaces the firmware's own announce parser/verifier (RetiTransportServer::noteAnnounce), which is removed; the SD-card announce log line moves to the handler. RETIMESH_UPSTREAM_FIXES and [env:t3s3-upstream] are gone; [env:t3s3-local] builds against sibling checkouts for library development.
|
Thanks for tracking this down and for adding a regression test. Losing writes after a direct Two things before this can go in:
Since maintainer edits aren't enabled on the branch, please push these yourself. Reviews may take a while on my end, but this one is close. |
|
Thanks again for finding this and for the regression test. Closing the active segment inside compact() is the right fix, because it covers direct calls as well as the internal callers. I'm merging this as-is and will handle the remaining issues myself in a follow-up commit, so you don't need to make any changes. The follow-up will:
This also covers #11, which fixed the threshold-triggered path on its own. |
Follow-up to #6. compact() now flushes and closes the active segment itself, runs the compaction body (moved to compact_segments()), and reopens min(current_segment, segment_count - 1) on every exit path. Fixes three issues in the #6 approach: - a failed rotation-triggered compaction created a stray seg<segment_count>.dat that clear() and finalize never remove - the early return when compact.tmp cannot be opened left the store with no active segment, so threshold-triggered and direct callers failed every later write until reboot - a failed rename in finalize_compaction() could reopen an out-of-range segment This also covers the threshold-path fix proposed in #11. Power loss: the flush and close happen before the COMPACTING journal is written, and the reopen happens after the journal is cleared (or deliberately retained for boot-time recovery). No segment writes occur in between, so crash recovery is unchanged. Adds RAM-fs open/write failure injection and tests for direct, threshold and rotation compaction failures.
|
Thanks for taking this over the line, and for the follow-up. I've read 84e09a6, bed1195 and d5282e6 against what you listed. The flush before the close, the reopen on every exit path, the clamp to While checking the clamp I found an older edge case in the same area, so I've filed it separately as #17 with a fix in #18. With On your question in #11: a T-Beam (ESP32, LittleFS) has run on master + microReticulum for 30 minutes with no "Has open FD", but path-store compaction hadn't triggered in that window yet. I'll confirm there once a node has logged a compaction. |
compact() deletes every source segment, including the one currently open for appends. LittleFS and FatFS refuse to unlink an open file, so on those filesystems the active segment survived compaction ("Failed to unlink ... Has open FD") and on POSIX-like filesystems the handle silently kept writing into an unlinked inode.
Close the active file before phase 1 and reopen a segment on both exit paths: seg0 after a successful compaction (the internal callers then move on to seg1 as before), current_segment after a failed one, so a direct compact() call never leaves the store without a writable segment.
Adds test_file_store_compact_keeps_store_writable, which fails on master (the record written after compact() is lost on reload) and passes here.